export const naturalSort = (a, b) => {
	const re = /(\d+)|([a-z]+)|([A-Z]+)|(.)/g;
	const aParts = [];
	let match;
	while ((match = re.exec(a))) {
		if (match[1]) aParts.push({ type: "num", value: parseInt(match[1], 10) });
		else if (match[2]) aParts.push({ type: "lower", value: match[2] });
		else if (match[3]) aParts.push({ type: "upper", value: match[3] });
		else if (match[4]) aParts.push({ type: "other", value: match[4] });
	}
	const bParts = [];
	while ((match = re.exec(b))) {
		if (match[1]) bParts.push({ type: "num", value: parseInt(match[1], 10) });
		else if (match[2]) bParts.push({ type: "lower", value: match[2] });
		else if (match[3]) bParts.push({ type: "upper", value: match[3] });
		else if (match[4]) bParts.push({ type: "other", value: match[4] });
	}
	const len = Math.min(aParts.length, bParts.length);
	for (let i = 0; i < len; i++) {
		const aPart = aParts[i];
		const bPart = bParts[i];
		if (aPart.type === "num" && bPart.type === "num") {
			if (aPart.value !== bPart.value) return aPart.value - bPart.value;
			continue;
		}
		if (aPart.type === "num" && bPart.type !== "num") return -1;
		if (aPart.type !== "num" && bPart.type === "num") return 1;
		if (aPart.type === "lower" && bPart.type === "lower") {
			const cmp = aPart.value.localeCompare(bPart.value);
			if (cmp !== 0) return cmp;
			continue;
		}
		if (aPart.type === "upper" && bPart.type === "upper") {
			const cmp = aPart.value.localeCompare(bPart.value);
			if (cmp !== 0) return cmp;
			continue;
		}
		if (aPart.type === "lower" && bPart.type === "upper") return -1;
		if (aPart.type === "upper" && bPart.type === "lower") return 1;
		const cmp = aPart.value.localeCompare(bPart.value);
		if (cmp !== 0) return cmp;
	}
	return aParts.length - bParts.length;
};
